﻿// Copyright (c) 2012 - 2020 Jared Zheng <jaredz at outlook dot com>
// Buddha Framework is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//
// Create : 2012-12-01 version 0.1 Jared Zheng <jaredz at outlook dot com>
// Update : 2018-12-05 version 3.1.5 Jared Zheng <jaredz at outlook dot com>

#include "stdafx.h"
#include "CommonServer.h"
#include "authorpacket.h"
#include "RoutineImp.h"

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// CAuthorRoutine
IMPLEMENT_RTTI_CREATE( CAuthorRoutine, CObject, CObjectTraits::OBJECTT_NONE )

UInt CAuthorRoutine::OnHandle(Int nEvent, uintptr_t utData, ULLong ullParam)
{
    switch (nEvent)
    {
    case PAKE_AUTHOR_VISA:
        {
            if (m_uVisaMode == VISA_MODE_NONE)
            {
                OnMasterVisa(ullParam);
            }
            else
            {
                OnMasterVisaManual(utData, ullParam);
            }
            return RET_OKAY;
        }
        break;
    case PAKE_TCP_CLOSED:
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]OnHandle(uintptr_t) PAKE_TCP_CLOSED ---> %p"), ullParam);
#endif
            // 连接断开, 直接删除对应数据
            Int nStatus = SESSION_STATUS_DEFAULT;
            {
                CSyncLockWaitScope scope(m_SessionLock);
                m_SessionIndex.Remove(ullParam);
                AUTHOR_SESSION_MAP_PAIR* pPair = m_SessionMap.Find(ullParam);
                if (pPair != nullptr)
                {
                    pPair->m_V->SetIndex(nullptr);
                    nStatus = pPair->m_V->GetStatus();
                }
                m_SessionMap.RemoveAt(reinterpret_cast<PINDEX>(pPair));
            }
            if (nStatus >= SESSION_STATUS_VISA)
            {
                CAtomics::Increment<UInt>(&m_uVisaComplete);
            }
            m_pServer->GetServerInfo(PAKT_AUTHOR)->DecrOnline();
        }
        break;
    case PAKE_LINKACK:
        {
            if (ullParam == PAKT_CENTER)
            {
                m_uCenterId = (UInt)utData;
            }
            else
            {
                assert(ullParam == PAKT_AUTHORDB);
                m_uAuthorDBId    = (UInt)utData;
                m_uMaintainBegin = 0;
                m_uMaintainEnd   = 0;
            }
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]OnHandle(uintptr_t) PAKE_LINKACK ---> %d"), ullParam);
#endif
        }
        break;
    case PAKE_UNLINKACK:
        {
            if (ullParam == PAKT_CENTER)
            {
                m_uCenterId = 0;
            }
            else
            {
                assert(ullParam == PAKT_AUTHORDB);
                m_uAuthorDBId = 0;
            }
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]OnHandle(uintptr_t) PAKE_UNLINKACK ---> %d"), ullParam);
#endif
        }
        break;
    default: {}
    }
    if (m_BusinessPtr != nullptr)
    {
        m_BusinessPtr->OnHandle(nEvent, utData, ullParam);
    }
    return RET_OKAY;
}

UInt CAuthorRoutine::OnHandle(Int nEvent, CEventBase& EventRef, ULLong ullParam)
{
    switch (nEvent)
    {
    case PAKE_AUTHOR_BOND:
        {
            OnExternalBond(EventRef, ullParam);
        }
        break;
    case PAKE_AUTHOR_LOGIN: // 默认登陆
        {
            OnExternalLogin(EventRef, ullParam);
        }
        break;
    // AuthorDB
    case PAKE_AUTHOR_MAINTAINACK:
        {
            OnAuthorDBMaintainAck(EventRef, ullParam);
        }
        break;
    // Master or Gate
    case PAKE_AUTHOR_VISAACK:
        {
            OnMasterVisaAck(EventRef, ullParam);
        }
        break;
    case PAKE_AUTHOR_CHECKACK:
        {
            OnMasterCheckAck(EventRef, ullParam);
        }
        break;
    case PAKE_AUTHOR_COMPLETEACK:
        {
            OnMasterCompleteAck(EventRef, ullParam);
        }
        break;
    case PAKE_AUTHOR_MAINTAIN:
    case PAKE_AUTHOR_LOGOUT:
    case PAKE_AUTHOR_UPGRADE:
    case PAKE_AUTHOR_LIST:
    case PAKE_AUTHOR_SELECT:
    case PAKE_AUTHOR_VISA:
    case PAKE_AUTHOR_CHECK:
    case PAKE_AUTHOR_COMPLETE:
    case PAKE_AUTHOR_FAULT:
    case PAKE_AUTHOR_RESERVED:
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p错误请求[%d], 关闭连接"), ullParam, nEvent);
#endif
            m_pServer->GetNetworkPtr()->Destroy((Socket_t)ullParam);
        }
        break;
    default:
        {
            if ((nEvent > PAKE_AUTHOR_RESERVED) && (nEvent < PAKE_AUTHOR_ACK))
            {
                OnExternalCustom(EventRef, ullParam);
            }
            else if ((nEvent > PAKE_AUTHOR_RESERVEDACK) && (nEvent <= PAKE_AUTHOR_END))
            {
                OnInternalCustom(EventRef, ullParam);
            }
        }
    }
    return RET_OKAY;
}

UInt CAuthorRoutine::OnHandle(Int nEvent, CStream& Stream, ULLong ullParam)
{
    switch (nEvent)
    {
    case PAKE_AUTHOR_LOGINACK:
        {
            SESSION_ID sid;
            MM_SAFE::Set(&sid, 0, sizeof(SESSION_ID));
            if (Stream.Size() > Stream.Tell())
            {
                Stream.Read(&sid, sizeof(SESSION_ID));
            }

            CAuthorSessionPtr SessionPtr;
            if (GetSession(ullParam, SessionPtr) && SessionPtr->TrySetStatusLTOE(SESSION_STATUS_LOGINWAIT, SESSION_STATUS_LOGINACK))
            {
                SessionPtr->SetVer(sid.uVer);
                SessionPtr->SetTarget(sid.uTarget);
                SessionPtr->SetType(sid.uType);
                SessionPtr->SetId(sid.uId);
                SessionPtr->SetllId(sid.ullId);

                SessionPtr->SetUpdate(CPlatform::GetRunningTime());
                SessionPtr->SetTimeId(SESSION_ID_IDLE);
                SessionPtr->SetTryCount();
                m_pServer->GetNetworkPtr()->SetAttr((Socket_t)ullParam, PAKT_EXTERNAL_AUTHOR);

                CNETLoginAck Ack;
                Ack.SetSessionId(ullParam);
                CStream& StreamAck = Ack.Alloc(m_pServer->GetNetworkPtr());
                CalcVisaQueue(StreamAck, m_uVisaMode);
                Ack.Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());

                if (m_uVisaMode == VISA_MODE_NONE)
                {
                    InVisaQueue(ullParam);
                }
            }
#ifdef __DUMP_STEP_DETAIL__
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p在内部更新会话ID数据包信息时检测连接和会话映射关系失败或者状态错误=%d"), ullParam, (SessionPtr == nullptr) ? SESSION_STATUS_DEFAULT : SessionPtr->GetStatus());
            }
#endif
        }
        break;
    case PAKE_AUTHOR_LOGOUTACK: // !!!框架只能将session至为连接状态, 无法通知连接的业务区将对应连接断开, 需要业务例程自己通知业务区断开!!!
    case PAKE_AUTHOR_FAULTACK:
        {
            CAuthorSessionPtr SessionPtr;
            if (GetSession(ullParam, SessionPtr) && SessionPtr->TrySetStatusGTOE(SESSION_STATUS_LOGINACK, SESSION_STATUS_BOND))
            {
                SessionPtr->SetTarget(0);
                SessionPtr->SetType(0);
                SessionPtr->SetId(0);
                SessionPtr->SetllId(0);

                SessionPtr->SetUpdate(CPlatform::GetRunningTime());
                SessionPtr->SetTimeId(SESSION_ID_IDLE);
                SessionPtr->SetTryCount();
                {
                    CSyncLockWaitScope scope(m_SessionLock);
                    if ((SessionPtr->GetIndex() != nullptr) && (m_SessionIndex.Find(ullParam) == nullptr))
                    {
                        m_SessionIndex.Add(ullParam, SessionPtr->GetIndex());
                    }
                }
                m_pServer->GetNetworkPtr()->SetAttr((Socket_t)ullParam, PAKT_EXTERNAL_READY);

                CNETBondAck Ack;
                CStream& StreamAck = Ack.Alloc(m_pServer->GetNetworkPtr());
                StreamAck << (UInt)SESSION_BOND_LOGOUT << (UInt)nEvent << m_uAuthorDBId << m_uCenterId << m_uMaintainBegin << m_uMaintainEnd;
                Ack.Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());
            }
#ifdef __DUMP_STEP_DETAIL__
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p在内部登出会话时检测连接和会话映射关系失败或者状态错误=%d"), ullParam, (SessionPtr == nullptr) ? SESSION_STATUS_DEFAULT : SessionPtr->GetStatus());
            }
#endif
        }
        break;
    default:
        {
            if (m_BusinessPtr != nullptr)
            {
                m_BusinessPtr->OnHandle(nEvent, Stream, ullParam);
            }
        }
    }
    return RET_OKAY;
}

UInt CAuthorRoutine::OnHandle(Int nEvent, UInt)
{
    if (nEvent == QUEUE_TIME_ID)
    {
        CheckVisaQueue();
    }
    else if (nEvent == SESSION_ID_AUTHOR)
    {
        AUTHOR_SESSION_MAP CheckSessions;
        if (CheckSessionStatus(CheckSessions))
        {
            HandleSessionStatus(CheckSessions);
        }
    }
    return RET_OKAY;
}

bool CAuthorRoutine::Start(ICommonServer* pServer)
{
    m_pServer = pServer;
    if ((m_pServer != nullptr) && LoadExtConfig())
    {
        HASH_DUMP dump;
        dump.llUsedCount = 0xEEEEEEEE;
        dump.llUsedTick  = sizeof(ULLong);
        CHash::Dump(dump);

        m_pKeyPtr = reinterpret_cast<PULLong>(dump.llUsedCount);

        // 默认必须创建异步事件队列
        m_uThreads = (UInt)m_pServer->GetConfig().GetServerValue(CServerConfig::CFG_LOAD_AUTHOR);
        m_uThreads = CPlatform::CheckThread(m_uThreads);
        LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]异步事件队列处理线程=%d, %s"), m_uThreads, (m_uThreads > 1) ? TF("启用多线程并发处理") : TF("启用单线程处理"));
        m_nUpdate = 0;
        // 超时为更新周期的倍数
        SetTimeout();

        m_SessionMap.SetHash(MAP_HASH_MIN);
        m_SessionIndex.SetHash(MAP_HASH_MIN);
        m_VisaQueue.SetGrow(MAP_HASH_MIN);

        CEventQueue::CreateTickEvent(SESSION_ID_AUTHOR, m_uTimeoutTick, *this);
        CEventQueue::CreateTickEvent(QUEUE_TIME_ID, m_uVisaTick, *this);

        if (m_BusinessPtr != nullptr)
        {
            if (m_BusinessPtr->Start(pServer) == false) // 注意异步事件队列还未初始化
            {
                return false;
            }
        }
        if (CEventQueue::EventQueue(m_EventQueuePtr, *this, 0, (Int)m_uThreads))
        {
            m_EventQueuePtr->SetThreadEvent(PAKE_THREAD_START, PAKE_THREAD_STOP);
            return m_EventQueuePtr->Init();
        }
    }
    return false;
}

bool CAuthorRoutine::Pause(bool bPause)
{
    if (m_BusinessPtr != nullptr)
    {
        m_BusinessPtr->Pause(bPause);
    }
    return false;
}

bool CAuthorRoutine::Update(void)
{
    if (m_pServer->GetServerInfo(PAKT_AUTHOR)->IsMayDay() == false)
    {
        if (m_EventQueuePtr->CheckThreads(m_uTimeout))
        {
            m_pServer->GetServerInfo(PAKT_AUTHOR)->MayDay();
            DEV_ERROR(TF("[授权网关例程]事件队列线程异常, 进入异常状态"));
            LOG_ERROR(m_pServer->GetFileLog(), TF("[授权网关例程]事件队列线程异常, 进入异常状态"));
        }
    }
    if (CEventQueue::CheckTickEvent(SESSION_ID_AUTHOR, DATAD_TIME))
    {
        DEV_WARN(TF("[授权网关例程]超时检测线程异常, 重启定时器"));
        LOG_WARN(m_pServer->GetFileLog(), TF("[授权网关]超时检测线程异常, 重启定时器"));
        CEventQueue::DestroyTickEvent(SESSION_ID_AUTHOR);
        CEventQueue::CreateTickEvent(SESSION_ID_AUTHOR, m_uTimeoutTick, *this);
    }
    if (CEventQueue::CheckTickEvent(QUEUE_TIME_ID, DATAD_TIME))
    {
        DEV_WARN(TF("[授权网关例程]业务区签证流程线程异常, 重启定时器"));
        LOG_WARN(m_pServer->GetFileLog(), TF("[授权网关]业务区签证流程线程异常, 重启定时器"));
        CEventQueue::DestroyTickEvent(QUEUE_TIME_ID);
        CEventQueue::CreateTickEvent(QUEUE_TIME_ID, m_uVisaTick, *this);
    }
    // 定时更新线程不需要检测
    // 定时更新线程异常导致不向同步管理发送心跳包, 会导致本服务器离线

    // 定时检测扩展配置文件是否有修改
    ++m_nUpdate;
    if (m_nUpdate >= m_nTime)
    {
        m_nUpdate = 0;
        LoadExtConfig(true);
    }
    if (m_BusinessPtr != nullptr)
    {
        m_BusinessPtr->Update();
    }
    return true;
}

void CAuthorRoutine::Stop(void)
{
    CEventQueue::DestroyTickEvent(SESSION_ID_AUTHOR);
    CEventQueue::DestroyTickEvent(QUEUE_TIME_ID);
    if (m_BusinessPtr != nullptr)
    {
        m_BusinessPtr->Stop();
        CString strName = m_BusinessPtr->GetRTTI().GetName();
        m_BusinessPtr->Exit();
        m_BusinessPtr = nullptr;
        CObjectTraits::Unload<ICommonRoutine>(*strName);
    }
    if (m_EventQueuePtr != nullptr)
    {
        m_EventQueuePtr->Exit();
        m_EventQueuePtr = nullptr;
    }
    m_SessionMap.RemoveAll();
    m_SessionIndex.RemoveAll();
    m_VisaQueue.RemoveAll();

    m_pServer         = nullptr;
    m_pKeyPtr         = nullptr;
    m_nUpdate         = 0;
    m_uThreads        = 0;
    m_uTimeout        = 0;
    m_uCenterId       = 0;
    m_uAuthorDBId     = 0;
    m_uMaintainBegin  = 0;
    m_uMaintainEnd    = 0;
    m_uUpdateMajor    = 0;
    m_uUpdateMinor    = 0;

    m_uVisaMode       = VISA_MODE_NONE;
    m_uVisaTick       = QUEUE_TIME_TICK;
    m_uVisaQueue      = 0;
    m_uVisaComplete   = 0;

    m_uMasterTry      = SESSION_TIME_MASTER_TRY;
    m_uTimeoutTick    = SESSION_TIME_OUT_TICK;
    m_uTimeoutMin     = SESSION_TIME_OUT_MID;
    m_uTimeoutMax     = SESSION_TIME_OUT_MAX;
    m_uTimeoutIdle    = SESSION_TIME_OUT_IDLE;
    m_uTimeoutWait    = SESSION_TIME_OUT_WAIT;

    m_uCompleteMode   = COMPLETE_MODE_NONE;
    m_uCompleteTime   = SESSION_TIME_COMPLETE;
}

bool CAuthorRoutine::SetTimeout(UInt uTimeout)
{
    if (uTimeout == 0)
    {
        m_uTimeout = m_pServer->GetConfig().GetUpdateTick() * DATAD_TIME;
    }
    else
    {
        m_uTimeout = uTimeout;
        m_uTimeout = DEF::Max<UInt>(1, m_uTimeout);
    }
    return true;
}

bool CAuthorRoutine::AddQueue(Int nEvent, uintptr_t utData, ULLong ullParam, QUEUE_TYPE eType)
{
    if (m_pServer == nullptr)
    {
        return false;
    }
    // 入异步事件队列前的检测尽量不要处理复杂功能, 需要处理的建议进队列回调处理
    // 有加锁的建议进队列回调处理
    // "ServerAck"
    // PAKE_LINKACK/PAKE_UNLINKACK, id, center/authordb
    // 外网端断开
    // PAKE_TCP_CLOSED, live-data, socket
    bool bRet = false;
    if (m_BusinessPtr != nullptr)
    {
        bRet = m_BusinessPtr->AddQueue(nEvent, utData, ullParam, eType);
    }
    switch (nEvent)
    {
    case PAKE_LINKACK:    // 向管理服务器注册回复结果
    case PAKE_UNLINK:     // 向本服务器注册的注销/断开
    case PAKE_UNLINKACK:  // 向管理服务器注销/断开回复结果
    case PAKE_TCP_CLOSED: // 连接的外网端注销/断开
        {
            bRet = true;
        }
        break;
    default: {}
    }
    if (bRet)
    {
        bRet = m_EventQueuePtr->Add(nEvent, utData, ullParam, eType);
    }
    return bRet;
}

bool CAuthorRoutine::AddQueue(Int nEvent, CEventBase& EventRef, ULLong ullParam, QUEUE_TYPE eType)
{
    if (m_pServer == nullptr)
    {
        return false;
    }
    // 入异步事件队列前的检测尽量不要处理复杂功能, 需要处理的建议进队列回调处理
    // 有加锁的建议进队列回调处理
    // "CommonEvent"
    // [PAKE_COMMON_BEGIN, PAKE_COMMON_END], EventRef, PTCP_PARAM/PUDP_PARAM
    //
    // 授权对外功能
    // [PAKE_AUTHOR_BEGIN, PAKE_AUTHOR_ACK), EventRef, PTCP_PARAM
    // AuthorDB or Master, Gate
    // [PAKE_AUTHOR_ACK, PAKE_AUTHOR_END], EventRef, PUDP_PARAM
    bool bRet = false;
    if (m_BusinessPtr != nullptr)
    {
        bRet = m_BusinessPtr->AddQueue(nEvent, EventRef, ullParam, eType);
    }
    // 授权事件, 必须进入队列处理
    ULLong ullSocket = ullParam;
    if (eType > QUEUE_TYPE_MASK)
    {
        CNETTraits::PTCP_PARAM pTcp = reinterpret_cast<CNETTraits::PTCP_PARAM>(ullParam);
        ullSocket = pTcp->sSocket;
    }
    if ((nEvent >= PAKE_AUTHOR_BEGIN) && (nEvent < PAKE_AUTHOR_ACK))
    {
        bRet = true;
        if (nEvent == PAKE_AUTHOR_BOND)
        {
            bRet = OnExternalBondWait(EventRef, ullSocket);
        }
        if (bRet)
        {
            (static_cast<CNETSession*>(&EventRef))->Attach(ullParam, eType, m_pServer->GetNetworkPtr());
        }
    }
    // AuthorDB or Master, Gate
    else if ((nEvent >= PAKE_AUTHOR_ACK) && (nEvent <= PAKE_AUTHOR_END))
    {
        bRet = true;
        (static_cast<CNETSession*>(&EventRef))->Attach(ullParam, eType, m_pServer->GetNetworkPtr());
    }
    if (bRet)
    {
        bRet = m_EventQueuePtr->Add(nEvent, EventRef, ullSocket, (eType & QUEUE_TYPE_MASK));
    }
    return bRet;
}

bool CAuthorRoutine::AddQueue(Int nEvent, CStream& Stream, ULLong ullParam)
{
    if (m_pServer == nullptr)
    {
        return false;
    }
    // 入异步事件队列前的检测尽量不要处理复杂功能, 需要处理的建议进队列回调处理
    // 有加锁的建议进队列回调处理
    // "SyncInfo"---注意流对象可能是临时实例, 不能加入队列
    // PAKE_LINK/PAKE_UPDATE, Stream, center/authordb
    bool bRet = false;
    if (m_BusinessPtr != nullptr)
    {
        bRet = m_BusinessPtr->AddQueue(nEvent, Stream, ullParam);
    }
    if (bRet)
    {
        bRet = m_EventQueuePtr->Add(nEvent, Stream, ullParam);
    }
    return bRet;
}

bool CAuthorRoutine::LoadExtConfig(bool bUpdate)
{
    CStringFix strPath;
    m_pServer->GetConfig().GetServerExtConfig(CServerConfig::CFG_LOAD_AUTHOR, strPath);

    CFileAttr fa;
    m_pServer->GetConfig().GetFilePath().FileAttr(fa, *strPath);
    if (bUpdate)
    {
        if (fa.MTime(m_tExtConfig))
        {
            return true;
        }
        LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]定时读取文件修改时间%llX---%llX"), fa.MTime(), m_tExtConfig);
        m_tExtConfig = fa.MTime();
    }
    else
    {
        m_tExtConfig = fa.MTime();
        LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]初始化读取文件修改时间=%llX"), m_tExtConfig);
    }
    LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]ExtConfig=%s"), *strPath);

    CFileReadStream Reader;
    if (Reader.Create(*strPath))
    {
        CKVNode  KVNode;
        CKVStore KVStore;
        if (KVStore.Load(KVNode, Reader))
        {
            if (KVNode.Find(TF("Time")))
            {
                m_nTime = KVNode[TF("Time")].GetInt();
            }
            if (KVNode.Find(TF("Major")))
            {
                m_uUpdateMajor = KVNode[TF("Major")].GetUInt();
            }
            if (KVNode.Find(TF("Minor")))
            {
                m_uUpdateMinor = KVNode[TF("Minor")].GetUInt();
            }

            if (KVNode.Find(TF("VisaMode")))
            {
                m_uVisaMode = KVNode[TF("VisaMode")].GetUInt();
            }
            if (KVNode.Find(TF("VisaTick")))
            {
                m_uVisaTick = KVNode[TF("VisaTick")].GetUInt();
            }

            if (KVNode.Find(TF("MasterTry")))
            {
                m_uMasterTry = KVNode[TF("MasterTry")].GetUInt();
            }
            if (KVNode.Find(TF("TimeoutTick")))
            {
                m_uTimeoutTick = KVNode[TF("TimeoutTick")].GetUInt();
            }
            if (KVNode.Find(TF("TimeoutMin")))
            {
                m_uTimeoutMin = KVNode[TF("TimeoutMin")].GetUInt();
            }
            if (KVNode.Find(TF("TimeoutMax")))
            {
                m_uTimeoutMax = KVNode[TF("TimeoutMax")].GetUInt();
            }
            if (KVNode.Find(TF("TimeoutIdle")))
            {
                m_uTimeoutIdle = KVNode[TF("TimeoutIdle")].GetUInt();
            }
            if (KVNode.Find(TF("TimeoutWait")))
            {
                m_uTimeoutWait = KVNode[TF("TimeoutWait")].GetUInt();
            }

            if (KVNode.Find(TF("CompleteMode")))
            {
                m_uCompleteMode = KVNode[TF("CompleteMode")].GetUInt();
            }
            if (KVNode.Find(TF("CompleteTime")))
            {
                m_uCompleteTime = KVNode[TF("CompleteTime")].GetUInt();
            }

            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]Time=%d"),  m_nTime);

            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]Major=%u"), m_uUpdateMajor);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]Minor=%u"), m_uUpdateMinor);

            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]VisaMode=%u"), m_uVisaMode);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]VisaTick=%u"), m_uVisaTick);

            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]MasterTry=%u"),    m_uMasterTry);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]TimeoutTick=%u"),  m_uTimeoutTick);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]TimeoutMin=%u"),   m_uTimeoutMin);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]TimeoutMax=%u"),   m_uTimeoutMax);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]TimeoutIdle=%u"),  m_uTimeoutIdle);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]TimeoutWait=%u"),  m_uTimeoutWait);

            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]CompleteMode=%u"), m_uCompleteMode);
            LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]CompleteTime=%u"), m_uCompleteTime);


            bool bRet = true;
            if ((bUpdate == false) && (KVNode.Find(TF("BusinessRoutine"))))
            {
                bRet = LoadExtBusiness(KVNode);
            }
            if (m_BusinessPtr != nullptr)
            {
                bRet = (m_BusinessPtr->OnHandle(bUpdate ? PAKE_CONFIG_UPDATE : PAKE_CONFIG_LOAD,
                                                reinterpret_cast<uintptr_t>(&KVNode),
                                                (ULLong)m_pServer) == RET_OKAY);
            }
            return bRet;
        }
        else
        {
            LOGV_WARN(m_pServer->GetFileLog(), TF("[授权网关例程]解析ExtConfig=%s文件内容失败"), *strPath);
        }
    }
    else
    {
        LOGV_WARN(m_pServer->GetFileLog(), TF("[授权网关例程]打开ExtConfig=%s文件不存在"), *strPath);
    }
    return true;
}

bool CAuthorRoutine::LoadExtBusiness(CKVNode& Root)
{
    CKVString strName;
    CKVString strObject;
    CKVString strModule;

    CKVNode& Routine = Root[TF("BusinessRoutine")];
    Routine[TF("Name")].GetString(strName);
    Routine[TF("Object")].GetString(strObject);
    Routine[TF("Module")].GetString(strModule);
#if (__PLATFORM_TARGET__ == PLATFORM_TARGET_LINUX)
    strModule.Insert(0, CServerConfig::LibPrefix);
#endif
    strModule += CServerConfig::LibExt;

    assert(m_BusinessPtr == nullptr);
    if (CObjectTraits::Load<ICommonRoutine>(*strName, *strModule, m_BusinessPtr, *strObject))
    {
        LOGV_INFO(m_pServer->GetFileLog(), TF("[授权网关例程]RTTI load --- Object=%s, Name=%s, Module=%s --- %p"), *strObject, *strName, *strModule, m_BusinessPtr.Get());
        if (m_BusinessPtr->Init() == (UInt)RET_OKAY)
        {
            m_BusinessPtr->OnHandle(PAKE_CONFIG_DEFAULT, reinterpret_cast<uintptr_t>(this), (ULLong)m_pServer);
            return true;
        }
    }
    LOGV_ERROR(m_pServer->GetFileLog(), TF("[授权网关例程]RTTI load --- Object=%s, Name=%s, Module=%s --- Failed!"), *strObject, *strName, *strModule);
    return false;
}

bool CAuthorRoutine::OnExternalBondWait(CEventBase& EventRef, ULLong ullParam)
{
    // 连接数量 - 有效数量 * usIncr, 增加毫秒的登陆延迟
    UInt uWait = 0;
    if (m_pServer->GetServerInfo(PAKT_AUTHOR)->uTotal > m_pServer->GetServerInfo(PAKT_AUTHOR)->uOnline)
    {
        uWait = (m_pServer->GetServerInfo(PAKT_AUTHOR)->uTotal - m_pServer->GetServerInfo(PAKT_AUTHOR)->uOnline);
    }
    uWait *= m_pServer->GetServerInfo(PAKT_AUTHOR)->usIncr;

    m_pServer->GetNetworkPtr()->SetAttr((Socket_t)ullParam, PAKT_EXTERNAL_READY);
    m_pServer->GetServerInfo(PAKT_AUTHOR)->IncrOnline();

    // 无法直接重用网络层缓存, 异步事件队列处理需要使用
    CNETBondAck Ack;
    Ack.SetSrvId((USRVID)m_pServer->GetServerInfo(PAKT_AUTHOR)->usId); // 授权网关的id
    Ack.SetSessionId(ullParam);
    CStream& Stream = Ack.Alloc(m_pServer->GetNetworkPtr());
    if (uWait >= DATAD_OVER_LOAD)
    {
        Ack.SetAck((UACK)ERRORD_MAYDAY);
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p连接回复申请网关服务器异常错误!!!"), ullParam);
#endif
    }
    else
    {
        Stream << (UInt)SESSION_BOND_WAIT << uWait;
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p连接回复等待时间%d[MS]"), ullParam, uWait);
#endif
    }
    Ack.Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());

    if (uWait >= DATAD_OVER_LOAD)
    {
        m_pServer->GetServerInfo(PAKT_AUTHOR)->MayDay(); // 队列堵塞, 异常
        DEV_ERROR(TF("[授权网关例程]事件队列线程堵塞异常, 进入异常状态"));
        LOG_ERROR(m_pServer->GetFileLog(), TF("[授权网关例程]事件队列线程堵塞异常, 进入异常状态"));
        return false; // 异常, 不需要加入异步事件队列
    }
    return true;
}

bool CAuthorRoutine::OnExternalBond(CEventBase& EventRef, ULLong ullParam)
{
    CNETBond* pBond = static_cast<CNETBond*>(&EventRef);
    CStream& Stream = pBond->GetStream();
    // 如果后期版本对登陆数据有扩展, 按读取的版本判断是何版本再读取对应数据
    SESSION_BOND sid;
    {
        CStreamSeekScope scope(Stream);
        Stream.Read(&sid, sizeof(SESSION_BOND));
    }
    /////////////////////////////////////////////////////////////////////////////////
    // major & minor 大于0 + 更新业务排序结果有数据 + m_uCenterId != TRUE
    // 如果外网端版本不大于计划版本, 进入更新业务管理
    #pragma message("[授权网关例程]未支持直接跳过授权认证进入更新业务SESSION_BOND_UPDATE---(sd.uMajor, sd.uMinor)")
    #pragma message("[授权网关例程]未支持手动模式选择业务管理sd.uSelect, 定时获取每个业务管理所有业务服务器的状态")
    /////////////////////////////////////////////////////////////////////////////////
    CAuthorSessionPtr SessionPtr;
    UInt uRet = ERRORD_OKAY;
    {
        CSyncLockWaitScope scope(m_SessionLock);
        PINDEX index = m_SessionMap.FindIndex(ullParam);
        if (index == nullptr)
        {
            SessionPtr = MNEW CAuthorSession;
            index = m_SessionMap.Add(ullParam, SessionPtr);
            m_SessionIndex.Add(ullParam, index);
            SessionPtr->SetIndex(index);

            SessionPtr->SetUpdate(CPlatform::GetRunningTime());
            SessionPtr->SetStatus(SESSION_STATUS_BOND);
            SessionPtr->SetTimeId(SESSION_ID_IDLE);

            SessionPtr->SetVer(sid.uVer);
            SessionPtr->SetType(sid.uType);
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p连接会话数据初始化完成"), ullParam);
#endif
        }
        else
        {
            // 在表内的都是连接完成的, 错误状态操作
            m_SessionIndex.Remove(ullParam);
            m_SessionMap.RemoveAt(index);
            uRet = ROUTINE_ERROR_VOIDNESS_STATUS;
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p连接会话数据已经存在, 错误状态请求, 关闭连接"), ullParam);
#endif
        }
    }
    if (uRet != ERRORD_OKAY)
    {
        if (m_BusinessPtr != nullptr)
        {
            // !!!业务例程 : 错误操作
            m_BusinessPtr->OnHandle(PAKE_AUTHOR_FAULT, PAKE_AUTHOR_BOND, ullParam);
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p连接会话无效请求处理调用业务例程"), ullParam);
        }
        else
        {
            DEV_DUMP(TF("[授权网关例程]%p连接会话无效请求处理无业务例程, 直接走默认流程"), ullParam);
#endif
        }
        ResetAck(pBond, PAKE_AUTHOR_BONDACK, (UACK)uRet);
        pBond->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr(), true);
        return false;
    }
    pBond->SetSessionId(ullParam);
    uRet = CallSessionBusiness<CNETBond>(pBond, ullParam, SessionPtr);
    if (uRet == SESSION_STATUS_DEFAULT)
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p连接会话CallSessionBusiness返回默认流程, 回复外网端可以登陆信息"), ullParam);
#endif
        ResetAck(pBond, PAKE_AUTHOR_BONDACK);
        CStream& StreamAck = pBond->WriteStream(m_pServer->GetNetworkPtr());
        StreamAck << (UInt)SESSION_BOND_LOGIN << m_uAuthorDBId << m_uCenterId << m_uMaintainBegin << m_uMaintainEnd;
        pBond->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());
    }
    return true;
}

bool CAuthorRoutine::OnExternalLogin(CEventBase& EventRef, ULLong ullParam)
{
    UInt uRet = SESSION_STATUS_DEFAULT;
    CAuthorSessionPtr SessionPtr;

    CNETLogin* pLogin = static_cast<CNETLogin*>(&EventRef);
    if ((GetSession(ullParam, SessionPtr) == false) ||
        (pLogin->GetSessionId() != ullParam) || 
        (SessionPtr->CheckStatusLTOE(SESSION_STATUS_LOGINWAIT) == false))
    {
        UACK uAck = (SessionPtr == nullptr) ? (UACK)ROUTINE_ERROR_VOIDNESS_SESSION : (UACK)ROUTINE_ERROR_VOIDNESS_STATUS;
        if (m_BusinessPtr != nullptr)
        {
            if (SessionPtr != nullptr)
            {
                pLogin->SetAck((UACK)SessionPtr->GetTryCount()); // 登陆请求次数, 每次返回HANDLE累加1
            }
            // !!!业务例程 : 错误操作
            m_BusinessPtr->OnHandle(PAKE_AUTHOR_FAULT, PAKE_AUTHOR_LOGIN, ullParam);
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p登陆会话无效请求处理调用业务例程"), ullParam);
        }
        else
        {
            DEV_DUMP(TF("[授权网关例程]%p登陆会话无效请求处理无业务例程, 直接走默认流程"), ullParam);
#endif
        }
        ResetAck(pLogin, PAKE_AUTHOR_LOGINACK, uAck);
        pLogin->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr(), true);
        return false;
    }
    pLogin->SetType((UTYPE)PAKT_AUTHOR);
    uRet = CallSessionBusiness<CNETLogin>(pLogin, ullParam, SessionPtr);
    if (uRet == SESSION_STATUS_DEFAULT)
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p登陆会话CallSessionBusiness返回默认流程, 进入业务区流程"), ullParam);
#endif
        SessionPtr->SetUpdate(CPlatform::GetRunningTime());
        SessionPtr->SetStatus(SESSION_STATUS_LOGINACK);
        SessionPtr->SetTimeId(SESSION_ID_IDLE);
        SessionPtr->SetTryCount();
        m_pServer->GetNetworkPtr()->SetAttr((Socket_t)ullParam, PAKT_EXTERNAL_AUTHOR);

        CStream& Stream = pLogin->WriteStream(m_pServer->GetNetworkPtr());
        CalcVisaQueue(Stream, m_uVisaMode);
        ResetAck(pLogin, PAKE_AUTHOR_LOGINACK);
        pLogin->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());
        if (m_uVisaMode == VISA_MODE_NONE)
        {
            InVisaQueue(ullParam);
        }
    }
    return true;
}

bool CAuthorRoutine::OnExternalCustom(CEventBase& EventRef, ULLong ullParam)
{
    UInt uRet = SESSION_STATUS_DEFAULT;
    CAuthorSessionPtr SessionPtr;

    CNETSession* pCustom = static_cast<CNETSession*>(&EventRef);
    if ((GetSession(ullParam, SessionPtr) == false) ||
        (pCustom->GetSessionId() != ullParam))
    {
        if (m_BusinessPtr != nullptr)
        {
            if (SessionPtr != nullptr)
            {
                pCustom->SetAck((UACK)SessionPtr->GetTryCount()); // 每个状态请求次数, 每次返回HANDLE累加1
            }
            // !!!业务例程 : 错误操作
            m_BusinessPtr->OnHandle(PAKE_AUTHOR_FAULT, pCustom->GetEvent(), ullParam);
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p自定义[%d]无效请求处理调用业务例程"), ullParam, pCustom->GetEvent());
        }
        else
        {
            DEV_DUMP(TF("[授权网关例程]%p自定义[%d]无效请求处理无业务例程, 直接走默认流程"), ullParam, pCustom->GetEvent());
#endif
        }
        uRet = (pCustom->GetEvent() - PAKE_AUTHOR_RESERVED) + PAKE_AUTHOR_RESERVEDACK;
        ResetAck(pCustom, (UEVENT)uRet, (UACK)ROUTINE_ERROR_VOIDNESS_SESSION);
        pCustom->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr(), true);
        return false;
    }
    SessionPtr->SetUpdate(CPlatform::GetRunningTime());
    uRet = CallSessionBusiness<CNETSession>(pCustom, ullParam, SessionPtr);
    if (uRet == SESSION_STATUS_DEFAULT)
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p自定义[%d]会话CallSessionBusiness返回默认流程"), ullParam, pCustom->GetEvent());
#endif
        SessionPtr->IncrTryCount();
    }
    return true;
}

bool CAuthorRoutine::OnInternalCustom(CEventBase& EventRef, ULLong ullParam)
{
    CNETSession* pCustom = static_cast<CNETSession*>(&EventRef);
    ullParam = pCustom->GetSessionId();
    if (ullParam != 0) // 可能有不针对单个session的事件
    {
        CAuthorSessionPtr SessionPtr;
        if (GetSession(ullParam, SessionPtr) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p内部自定义[%d]无效请求处理调用业务例程"), ullParam, pCustom->GetEvent());
#endif
            return false;
        }
    }
    if (m_BusinessPtr != nullptr)
    {
        m_BusinessPtr->OnHandle((Int)pCustom->GetEvent(), *pCustom, ullParam);
    }
    return true;
}

bool CAuthorRoutine::OnAuthorDBMaintainAck(CEventBase& EventRef, ULLong ullParam)
{
    CNETMaintainAck* pMaintainAck = static_cast<CNETMaintainAck*>(&EventRef);
    if (pMaintainAck->GetType() == PAKT_AUTHORDB)
    {
        pMaintainAck->GetStream() >> m_uMaintainBegin >> m_uMaintainEnd;
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]授权数据服务器通知维护时间%llX---%llX"), m_uMaintainBegin, m_uMaintainEnd);
#endif
        return true;
    }
#ifdef __DUMP_STEP_DETAIL__
    DEV_DUMP(TF("[授权网关例程]授权数据服务器通知维护时间服务器类型错误=%d"), pMaintainAck->GetType());
#endif
    return false;
}

bool CAuthorRoutine::OnMasterVisa(ULLong ullParam)
{
    bool bRet = false;
    BEGIN_SCOPE( bRet, false );
    CAuthorSessionPtr SessionPtr;
    if (GetSession(ullParam, SessionPtr))
    {
        if (SessionPtr->OutTryCount(m_uMasterTry))
        {
            if (m_BusinessPtr != nullptr)
            {
                // !!!业务例程 : 错误操作
                m_BusinessPtr->OnHandle(PAKE_AUTHOR_FAULT, PAKE_AUTHOR_VISA, ullParam);
#ifdef __DUMP_STEP_DETAIL__
                DEV_DUMP(TF("[授权网关例程]%p业务区签证超处理次数[%d]调用业务例程"), ullParam, SessionPtr->GetTryCount());
            }
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p业务区签证超处理次数[%d]无业务例程, 直接走默认流程"), ullParam, SessionPtr->GetTryCount());
#endif
            }
            CPAKSession Ack(PAKE_AUTHOR_VISAACK, PAKT_AUTHOR, ROUTINE_ERROR_OVER_LIMIT);
            m_pServer->GetNetworkPtr()->Send((Socket_t)ullParam, Ack, CNETTraits::SEND_CLOSE);
            break;
        }
        if (SessionPtr->TrySetStatus(SESSION_STATUS_LOGINACK, SESSION_STATUS_VISA) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证会话状态错误%d---%d"), ullParam, SessionPtr->GetStatus(), SESSION_STATUS_LOGINACK);
#endif
            break;
        }
        SessionPtr->SetUpdate(CPlatform::GetRunningTime());
        SORT_RESULT& SortResult = m_pServer->GetServerSort(PAKT_MASTER)->GetCur();
        if (SortResult.uCount > 0)
        {
            UInt uIndex = SortResult.Index();

            CNetAddr adrMaster = ((PAUTHOR_ADDR)m_pServer->GetServerInfo(PAKT_AUTHOR))->NetAddr[AUTHORI_MASTER];

            CNETVisa Visa;
            Visa.SetSessionId(ullParam);
            CStream& Stream = Visa.Alloc(m_pServer->GetNetworkPtr(), true);
            adrMaster.Serialize(Stream);
            Stream.Write(SessionPtr->GetSession(), sizeof(SESSION_ID));
            if (m_BusinessPtr != nullptr)
            {
                // !!!业务例程 : 必须预留stSize大小在头部由框架例程写入SESSION_ID等数据
                m_BusinessPtr->OnHandle(PAKE_AUTHOR_VISA, Stream, ullParam);
#ifdef __DUMP_STEP_DETAIL__
                DEV_DUMP(TF("[授权网关例程]%p业务区签证写入数据调用业务例程处理"), ullParam);
            }
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p业务区签证写入数据无业务例程, 直接走默认流程"), ullParam);
#endif
            }
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证写入数据大小=%d, 发送给选中的业务管理服务器%llX"), ullParam, Stream.Tell(), SortResult.Sort[uIndex].ullKey);
#endif
            SessionPtr->SetTimeId(SESSION_ID_MIN);

            Visa.Addr(SortResult.Sort[uIndex].Addr);
            Visa.Send(m_pServer->GetSocket(PAKT_MASTER), m_pServer->GetNetworkPtr());
            bRet = true;
        }
        else
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证没有业务管理服务器在线"), ullParam);
#endif
            // 提示没有业务管理服务器, 等待业务管理连接完成再通知
            SessionPtr->SetStatus(SESSION_STATUS_VISAWAIT);
            SessionPtr->SetTimeId(SESSION_ID_VISAWAIT);

            CPAKSession Ack(PAKE_AUTHOR_VISAACK, PAKT_AUTHOR, ROUTINE_ERROR_REQ_MASTER);
            Ack.SetSessionId(ullParam);
            m_pServer->GetNetworkPtr()->Send((Socket_t)ullParam, Ack);
        }
    }
    else
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p业务区签证1没有找到对应会话"), ullParam);
#endif
    }
    END_SCOPE();
    if (bRet == false)
    {
        CAtomics::Increment<UInt>(&m_uVisaComplete);
        return false;
    }
    return true;
}

bool CAuthorRoutine::OnMasterVisaManual(ULLong ullMaster, ULLong ullParam)
{
    bool bRet = false;
    BEGIN_SCOPE( bRet, false );
    CAuthorSessionPtr SessionPtr;
    if (GetSession(ullParam, SessionPtr))
    {
        if (SessionPtr->OutTryCount(m_uMasterTry))
        {
            if (m_BusinessPtr != nullptr)
            {
                // !!!业务例程 : 错误操作
                m_BusinessPtr->OnHandle(PAKE_AUTHOR_FAULT, PAKE_AUTHOR_VISA, ullParam);
#ifdef __DUMP_STEP_DETAIL__
                DEV_DUMP(TF("[授权网关例程]%p业务区签证2超处理次数[%d]调用业务例程"), ullParam, SessionPtr->GetTryCount());
            }
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p业务区签证2超处理次数[%d]无业务例程, 直接走默认流程"), ullParam, SessionPtr->GetTryCount());
#endif
            }
            CPAKSession Ack(PAKE_AUTHOR_VISAACK, PAKT_AUTHOR, ROUTINE_ERROR_OVER_LIMIT);
            m_pServer->GetNetworkPtr()->Send((Socket_t)ullParam, Ack, CNETTraits::SEND_CLOSE);
            break;
        }
        if (SessionPtr->TrySetStatus(SESSION_STATUS_LOGINACK, SESSION_STATUS_VISA) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证2会话状态错误%d---%d"), ullParam, SessionPtr->GetStatus(), SESSION_STATUS_LOGINACK);
#endif
            break;
        }
        SessionPtr->SetUpdate(CPlatform::GetRunningTime());
        SORT_RESULT& SortResult = m_pServer->GetServerSort(PAKT_MASTER)->GetCur();
        UInt uIndex = SortResult.Find(ullMaster);
        if (uIndex != (UInt)-1)
        {
            CNetAddr adrMaster = ((PAUTHOR_ADDR)m_pServer->GetServerInfo(PAKT_AUTHOR))->NetAddr[AUTHORI_MASTER];

            CNETVisa Visa;
            Visa.SetSessionId(ullParam);
            CStream& Stream = Visa.Alloc(m_pServer->GetNetworkPtr(), true);
            adrMaster.Serialize(Stream);
            Stream.Write(SessionPtr->GetSession(), sizeof(SESSION_ID));
            if (m_BusinessPtr != nullptr)
            {
                // !!!业务例程 : 必须预留stSize大小在头部由框架例程写入SESSION_ID等数据
                m_BusinessPtr->OnHandle(PAKE_AUTHOR_VISA, Stream, ullParam);
#ifdef __DUMP_STEP_DETAIL__
                DEV_DUMP(TF("[授权网关例程]%p业务区签证2写入数据调用业务例程处理"), ullParam);
            }
            else
            {
                DEV_DUMP(TF("[授权网关例程]%p业务区签证2写入数据无业务例程, 直接走默认流程"), ullParam);
#endif
            }
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证2写入数据大小=%d, 发送给选中的业务管理服务器%llX"), ullParam, Stream.Tell(), ullMaster);
#endif
            SessionPtr->SetTimeId(SESSION_ID_MIN);

            Visa.Addr(SortResult.Sort[uIndex].Addr);
            Visa.Send(m_pServer->GetSocket(PAKT_MASTER), m_pServer->GetNetworkPtr());
            bRet = true;
        }
        else
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p业务区签证2没有特定的业务管理服务器[%p]在线"), ullParam, ullMaster);
#endif
            SessionPtr->SetStatus(SESSION_STATUS_LOGINACK);
            // 提示没有业务管理服务器
            CPAKSession Ack(PAKE_AUTHOR_VISAACK, PAKT_AUTHOR, ROUTINE_ERROR_REQ_MASTER);
            Ack.SetSessionId(ullParam);
            m_pServer->GetNetworkPtr()->Send((Socket_t)ullParam, Ack);
        }
    }
    else
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p业务区签证2没有找到对应会话"), ullParam);
#endif
    }
    END_SCOPE();
    if (bRet == false)
    {
        CAtomics::Increment<UInt>(&m_uVisaComplete);
        return false;
    }
    return true;
}

bool CAuthorRoutine::OnMasterVisaAck(CEventBase& EventRef, ULLong ullParam)
{
    bool bRet = false;
    BEGIN_SCOPE( bRet, false );
    // LLong[请求对应时间/异或外网端唯一性数据] + ULLong[令牌1^_^/时间查表异或] + UInt[业务管理id] + UInt[业务网关id] + CNetAddr[业务网关地址]
    CNETVisaAck* pVisaAck = static_cast<CNETVisaAck*>(&EventRef);

    ullParam = pVisaAck->GetSessionId();
    if ((pVisaAck->GetType() == PAKT_GATE) || (pVisaAck->GetType() == PAKT_MASTER))
    {
        pVisaAck->SetType((UTYPE)PAKT_AUTHOR);
        CAuthorSessionPtr SessionPtr;
        if (CheckSessionIncrement<CNETVisaAck>(pVisaAck, SESSION_STATUS_VISA, SESSION_STATUS_VISAACK, SessionPtr) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证ACK会话CheckSessionTime返回失败"), ullParam);
#endif
            break;
        }
        if (pVisaAck->GetAck() == (UACK)ERRORD_OKAY)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证ACK回复数据, 发送给外网端"), ullParam);
#endif
            pVisaAck->Addr().Reset();
            pVisaAck->ResetSend((Socket_t)ullParam, m_pServer->GetNetworkPtr(), pVisaAck->Length());
            bRet = true;
        }
        else
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证ACK回复错误=%d, 重新开始业务区流程"), ullParam, pVisaAck->GetAck());
#endif
            SessionPtr->SetUpdate(CPlatform::GetRunningTime());
            SessionPtr->SetStatus(SESSION_STATUS_LOGINACK);
            SessionPtr->SetTimeId(SESSION_ID_IDLE);
            if (pVisaAck->GetAck() == (UACK)ERRORD_MAYDAY)
            {
                SessionPtr->SetTryCount(); // 对应服务器异常, 清除尝试次数
            }
            pVisaAck->Addr().Reset();

            CStream& Stream = pVisaAck->WriteStream(m_pServer->GetNetworkPtr());
            CalcVisaQueue(Stream, m_uVisaMode);
            pVisaAck->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());

            if (m_uVisaMode == VISA_MODE_NONE)
            {
                InVisaQueue(ullParam);
            }
        }
    }
    else
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p签证ACK回复服务器类型错误=%d"), ullParam, pVisaAck->GetType());
#endif
    }
    END_SCOPE();
    if (bRet == false)
    {
        CAtomics::Increment<UInt>(&m_uVisaComplete);
        return false;
    }
    return true;
}

bool CAuthorRoutine::OnMasterCheckAck(CEventBase& EventRef, ULLong ullParam)
{
    bool bRet = false;
    BEGIN_SCOPE( bRet, false );
    // LLong[请求对应时间/异或外网端唯一性数据] + ULLong[令牌2^_^/时间查表异或]
    CNETCheckAck* pCheckAck = static_cast<CNETCheckAck*>(&EventRef);

    ullParam = pCheckAck->GetSessionId();
    if (pCheckAck->GetType() == PAKT_GATE)
    {
        pCheckAck->SetType((UTYPE)PAKT_AUTHOR);
        CAuthorSessionPtr SessionPtr;
        if (CheckSessionIncrement<CNETCheckAck>(pCheckAck, SESSION_STATUS_VISAACK, SESSION_STATUS_CHECK, SessionPtr) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证令牌会话CheckSessionTime返回失败"), ullParam);
#endif
            break;
        }
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p签证令牌会话回复数据, 发送给外网端"), ullParam);
#endif
        assert(pCheckAck->GetAck() == (UACK)ERRORD_OKAY);
        pCheckAck->Addr().Reset();
        pCheckAck->ResetSend((Socket_t)ullParam, m_pServer->GetNetworkPtr(), pCheckAck->Length());
        bRet = true;
    }
    else
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p签证令牌会话回复服务器类型错误=%d"), ullParam, pCheckAck->GetType());
#endif
    }
    END_SCOPE();
    if (bRet == false)
    {
        CAtomics::Increment<UInt>(&m_uVisaComplete);
        return false;
    }
    return true;
}

bool CAuthorRoutine::OnMasterCompleteAck(CEventBase& EventRef, ULLong ullParam)
{
    UInt uStatus = SESSION_STATUS_CHECK;

    bool bRet = false;
    BEGIN_SCOPE( bRet, false );
    CNETCompleteAck* pCompleteAck = static_cast<CNETCompleteAck*>(&EventRef);

    ullParam = pCompleteAck->GetSessionId();
    if (pCompleteAck->GetType() == PAKT_GATE)
    {
        pCompleteAck->SetType((UTYPE)PAKT_AUTHOR);
        if (pCompleteAck->GetAck() != (UACK)ERRORD_OKAY)
        {
            if (pCompleteAck->GetAck() == (UACK)ROUTINE_ERROR_KICK_LOGOUT) // 踢除, 授权直接关闭
            {
                pCompleteAck->Addr().Reset();
                pCompleteAck->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr(), true);
                uStatus = SESSION_STATUS_HANDLE;
                break;
            }
            // 完成只有业务区网关连接断开, 但是授权未断开的时候会收到
            uStatus = SESSION_STATUS_COMPLETE;
        }
        CAuthorSessionPtr SessionPtr;
        if (CheckSessionIncrement<CNETCompleteAck>(pCompleteAck, uStatus, SESSION_STATUS_COMPLETE, SessionPtr) == false)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证完成会话CheckSessionTime返回失败"), ullParam);
#endif
            break;
        }
        SessionPtr->SetUpdate(CPlatform::GetRunningTime());
        SessionPtr->SetTryCount();
        if (pCompleteAck->GetAck() == (UACK)ERRORD_OKAY)
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证完成"), ullParam);
#endif
            SessionPtr->SetTimeId(SESSION_ID_MAX);
            if (m_uCompleteMode == COMPLETE_MODE_LIVE)
            {
                CSyncLockWaitScope scope(m_SessionLock);
                m_SessionIndex.Remove(ullParam);
            }

            pCompleteAck->Addr().Reset();
            pCompleteAck->SetSessionId(ullParam);
            pCompleteAck->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());
            bRet = true;
        }
        else
        {
#ifdef __DUMP_STEP_DETAIL__
            DEV_DUMP(TF("[授权网关例程]%p签证完成会话回复断开=%d, 重新开始业务区流程"), ullParam, pCompleteAck->GetAck());
#endif
            SessionPtr->SetTimeId(SESSION_ID_IDLE);
            SessionPtr->SetStatus(SESSION_STATUS_LOGINACK);
            if ((m_uCompleteMode == COMPLETE_MODE_LIVE) && (uStatus == SESSION_STATUS_COMPLETE))
            {
                CSyncLockWaitScope scope(m_SessionLock);
                if ((SessionPtr->GetIndex() != nullptr) && (m_SessionIndex.Find(ullParam) == nullptr))
                {
                    m_SessionIndex.Add(ullParam, SessionPtr->GetIndex());
                }
            }

            pCompleteAck->Addr().Reset();

            CStream& Stream = pCompleteAck->WriteStream(m_pServer->GetNetworkPtr());
            CalcVisaQueue(Stream, m_uVisaMode);
            pCompleteAck->SetSessionId(ullParam);
            pCompleteAck->Send((Socket_t)ullParam, m_pServer->GetNetworkPtr());

            if (m_uVisaMode == VISA_MODE_NONE)
            {
                InVisaQueue(ullParam);
            }
        }
    }
    else
    {
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]%p签证完成会话回复服务器类型错误=%d"), ullParam, pCompleteAck->GetType());
#endif
    }
    END_SCOPE();
    if (uStatus >= SESSION_STATUS_CHECK)
    {
        CAtomics::Increment<UInt>(&m_uVisaComplete);
    }
    return bRet;
}

bool CAuthorRoutine::CheckVisaQueue(void)
{
    UInt uTemp = CAtomics::CompareExchange<UInt>(&m_uVisaComplete, 0, 0);
    if ((uTemp != 0) && (m_uVisaQueue <= (uTemp + VISA_QUEUE_DEFAULT)))
    {
        CSyncLockWaitScope scope(m_VisaLock);
        if (m_uVisaQueue <= uTemp)
        {
            CAtomics::Exchange<UInt>(&m_uVisaComplete, 0);
            m_uVisaQueue = 0;
        }
        uTemp = 0;
        AUTHOR_VISA Visa;
        while (m_VisaQueue.GetSize() > 0)
        {
            Visa = m_VisaQueue.Out();
            if (m_pServer->GetNetworkPtr()->GetAttr((Socket_t)Visa.ullAuthor) != 0)
            {
                m_EventQueuePtr->Add(PAKE_AUTHOR_VISA, Visa.ullMaster, Visa.ullAuthor);
                ++uTemp;
                if (uTemp >= VISA_QUEUE_LIMIT)
                {
                    break;
                }
            }
        }
        m_uVisaQueue += uTemp;
#ifdef __DUMP_STEP_DETAIL__
        DEV_DUMP(TF("[授权网关例程]!!!!!!!!!!!!!!!!!!!!!签证申请-签证排队数量=%d, 申请数量=%d, 完成数量=%d"), m_VisaQueue.GetSize(), m_uVisaQueue, m_uVisaComplete);
#endif
        return true;
    }
    return false;
}

bool CAuthorRoutine::CheckSessionStatus(AUTHOR_SESSION_MAP& CheckSessions)
{
    CSyncLockWaitScope scope(m_SessionLock);
    for (PINDEX index = m_SessionIndex.GetFirstIndex(); index != nullptr; )
    {
        AUTHOR_INDEX_MAP_PAIR*   pIndex = m_SessionIndex.GetNext(index);
        AUTHOR_SESSION_MAP_PAIR* pPair  = m_SessionMap.GetAt(pIndex->m_V);
        if (m_SessionMap.Check(pIndex->m_V) == false)
        {
            DEV_WARN(TF("[授权网关例程]!!!!!!!!!!!!!!!!!!!!!%p---%p---%p   %p---%p"), pIndex->m_K, pIndex->m_V, pPair, pPair->m_K, pPair->m_V.Get());
            m_SessionMap.Remove(pIndex->m_K);
            m_SessionIndex.RemoveAt(reinterpret_cast<PINDEX>(pIndex));
            continue;
        }
        switch (pPair->m_V->GetTimeId())
        {
        case SESSION_ID_MIN:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutMin))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        case SESSION_ID_MAX:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutMax))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        case SESSION_ID_IDLE:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutIdle))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        case SESSION_ID_WAIT:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutWait))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        case SESSION_ID_LOGINWAIT:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutWait))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
                else if (m_uAuthorDBId != 0)
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        case SESSION_ID_VISAWAIT:
            {
                if (pPair->m_V->TimeoutUpdate((LLong)m_uTimeoutWait))
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
                else if (m_pServer->GetServerSort(PAKT_MASTER)->GetCur().uCount > 0)
                {
                    CheckSessions.Add(pIndex->m_K, pPair->m_V);
                }
            }
            break;
        default:
            {
            }
        }
    }
    return (CheckSessions.GetSize() > 0);
}

bool CAuthorRoutine::HandleSessionStatus(AUTHOR_SESSION_MAP& CheckSessions)
{
    for (PINDEX index = CheckSessions.GetFirstIndex(); index != nullptr; )
    {
        AUTHOR_SESSION_MAP_PAIR* pPair = CheckSessions.GetNext(index);
        UInt uStatus = pPair->m_V->GetStatus();
        switch (uStatus)
        {
        case SESSION_STATUS_VISA:
        case SESSION_STATUS_VISAACK:
        case SESSION_STATUS_CHECK:
            {
                if (pPair->m_V->TrySetStatus(uStatus, SESSION_STATUS_LOGINACK))
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p在状态[%d]超时重新开始业务区流程%llX---%llX"), pPair->m_K, uStatus, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    pPair->m_V->SetUpdate(CPlatform::GetRunningTime());
                    pPair->m_V->SetTimeId(SESSION_ID_IDLE);

                    CAtomics::Increment<UInt>(&m_uVisaComplete);

                    CNETVisaAck Ack((UACK)ROUTINE_ERROR_ACK_TIMEOUT);
                    Ack.SetType((UTYPE)PAKT_AUTHOR);
                    Ack.SetSessionId((ULLong)pPair->m_K);
                    CStream& Stream = Ack.Alloc(m_pServer->GetNetworkPtr());
                    CalcVisaQueue(Stream, m_uVisaMode);
                    Ack.Send((Socket_t)pPair->m_K, m_pServer->GetNetworkPtr());

                    if (m_uVisaMode == VISA_MODE_NONE)
                    {
                        InVisaQueue(pPair->m_K);
                    }
                }
            }
            break;
        case SESSION_STATUS_LOGIN:
            {
                // 超时等待将向外网端发送默认(UEVENT)PAKE_AUTHOR_LOGINACK + (UACK)ROUTINE_ERROR_ACK_TIMEOUT + 2-UINT授权数据维护时间信息
                // 状态切换为SESSION_STATUS_LOGINWAIT
                if (pPair->m_V->TrySetStatus(SESSION_STATUS_LOGIN, SESSION_STATUS_LOGINWAIT))
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p等待授权数据回复超时切换到等待授权数据状态%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    pPair->m_V->SetUpdate(CPlatform::GetRunningTime());
                    pPair->m_V->SetTimeId(SESSION_ID_LOGINWAIT);

                    CPAKLoginAck Ack((UACK)ROUTINE_ERROR_ACK_TIMEOUT);
                    Ack.SetSessionId(pPair->m_K);
                    m_pServer->GetNetworkPtr()->Send((Socket_t)pPair->m_K, Ack);
                }
            }
            break;
        case SESSION_STATUS_COMPLETE:
            {
                if (pPair->m_V->IncrTryCount() >= m_uCompleteTime)
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p业务区流程完成关闭连接%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    m_pServer->GetNetworkPtr()->Destroy(pPair->m_K);
                }
                else
                {
                    pPair->m_V->SetUpdate(CPlatform::GetRunningTime());
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p业务区流程完成准备关闭连接%llX---%llX[%d]"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime(), pPair->m_V->GetTryCount());
#endif
                }
            }
            break;
        case SESSION_STATUS_BOND:
        case SESSION_STATUS_LOGINACK:
            {
#ifdef __DUMP_STEP_DETAIL__
                DEV_DUMP(TF("[授权网关例程]%p在状态[%d]超时关闭连接%llX---%llX"), pPair->m_K, uStatus, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                CPAKFaultAck Ack((UACK)ROUTINE_ERROR_IDLE_TIMEOUT);
                m_pServer->GetNetworkPtr()->Send((Socket_t)pPair->m_K, Ack, CNETTraits::SEND_CLOSE);
            }
            break;
        case SESSION_STATUS_LOGINWAIT:
            {
                if (m_uAuthorDBId == 0)
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p等待授权数据上线超时关闭连接%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    CPAKFaultAck Ack((UACK)ROUTINE_ERROR_WAIT_LIMIT);
                    m_pServer->GetNetworkPtr()->Send((Socket_t)pPair->m_K, Ack, CNETTraits::SEND_CLOSE);
                }
                else if (pPair->m_V->TrySetStatus(SESSION_STATUS_LOGINWAIT, SESSION_STATUS_BOND))
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p授权数据上线发送可以重新登陆信息%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    pPair->m_V->SetUpdate(CPlatform::GetRunningTime());
                    pPair->m_V->SetTimeId(SESSION_ID_IDLE);

                    CNETBondAck Ack;
                    Ack.SetSessionId((ULLong)pPair->m_K);
                    CStream& Stream = Ack.Alloc(m_pServer->GetNetworkPtr());
                    Stream << (UInt)SESSION_BOND_RESUME << m_uAuthorDBId << m_uCenterId;
                    Ack.Send((Socket_t)pPair->m_K, m_pServer->GetNetworkPtr());
                }
            }
            break;
        case SESSION_STATUS_VISAWAIT:
            {
                if (m_pServer->GetServerSort(PAKT_MASTER)->GetCur().uCount > 0)
                {
                    if (pPair->m_V->TrySetStatus(SESSION_STATUS_VISAWAIT, SESSION_STATUS_LOGINACK))
                    {
#ifdef __DUMP_STEP_DETAIL__
                        DEV_DUMP(TF("[授权网关例程]%p等待业务管理上线进入业务区流程%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                        pPair->m_V->SetUpdate(CPlatform::GetRunningTime());
                        pPair->m_V->SetTimeId(SESSION_ID_IDLE);
                        CAtomics::Increment<UInt>(&m_uVisaComplete);

                        CNETVisaAck Ack((UACK)ROUTINE_ERROR_REQ_MASTER);
                        Ack.SetType((UTYPE)PAKT_AUTHOR);
                        Ack.SetSessionId((ULLong)pPair->m_K);
                        CStream& Stream = Ack.Alloc(m_pServer->GetNetworkPtr());
                        CalcVisaQueue(Stream, m_uVisaMode);
                        Ack.Send((Socket_t)pPair->m_K, m_pServer->GetNetworkPtr());

                        if (m_uVisaMode == VISA_MODE_NONE)
                        {
                            InVisaQueue(pPair->m_K);
                        }
                    }
                }
                else
                {
#ifdef __DUMP_STEP_DETAIL__
                    DEV_DUMP(TF("[授权网关例程]%p等待业务管理上线超时关闭连接%llX---%llX"), pPair->m_K, pPair->m_V->GetUpdate(), CPlatform::GetRunningTime());
#endif
                    CPAKFaultAck Ack((UACK)ROUTINE_ERROR_WAIT_LIMIT);
                    m_pServer->GetNetworkPtr()->Send((Socket_t)pPair->m_K, Ack, CNETTraits::SEND_CLOSE);
                }
            }
            break;
        default:
            {
            }
        }
    }
    return true;
}

#if (__PLATFORM_TARGET__ == PLATFORM_TARGET_WINDOWS)
// Windows Realse版本MSVC编译器如果不强制调用静态常量, 静态常量会被优化掉
BOOL WINAPI DllMain(HMODULE hModule, ULong ulReason, void* lpReserved)
{
    UNREFERENCED_PARAMETER( lpReserved );

    if (ulReason == DLL_PROCESS_ATTACH)
    {
        ::DisableThreadLibraryCalls(hModule);
        DEV_DUMP(TF("$ Link %s.dll --- DLL_PROCESS_ATTACH"), CAuthorRoutine::ms_RTTI.GetName());
    }
    else if (ulReason == DLL_PROCESS_DETACH)
    {
        DEV_DUMP(TF("$ Unlink %s.dll --- DLL_PROCESS_DETACH"), CAuthorRoutine::ms_RTTI.GetName());
    }
    return TRUE;
}
#endif
